var fs = require('fs');
var path = require('path');
var fileUtil = require('../sys/fileUtil');
var template = require('./template');

class response {
    constructor(req, res, sessionManager, interceptorAfter) {
        this.req = req;
        this.res = res;
        this.write = res.write;
        this.end = res.end;

        res.setTimeout(sysConfig.responseTimeout, () => {
            res.send500(new Error(req.url + ' 响应超时'));
        });
        res.on('finish', () => {
            log4js.getLogger('access').info(req.method, req.url, req.getClientIp(), 'HTTP/' + req.httpVersion, res.statusCode, req.headers['user-agent']);
        });


        res.disableCache = false;
        res.isBinary = false;
        res.write = (chunk, encoding, callback) => {
            this.writeOrEnd(sessionManager, interceptorAfter, chunk, encoding, callback, false);
        };
        res.end = (chunk, encoding, callback) => {
            this.writeOrEnd(sessionManager, interceptorAfter, chunk, encoding, callback, true);
        };
        res.send = (chunk, encoding) => {
            res.end(chunk.toStr(), encoding);
        };
        res.view = (filePath, isStaticOrData) => {
            if (isStaticOrData && typeof isStaticOrData === 'object') {
                filePath = path.join(process.cwd(), filePath);
                var tpl = new template(filePath);
                tpl.parse(isStaticOrData).then(html => {
                    res.send(html);
                }).catch(err => {
                    res.send500(err);
                });
            } else {
                filePath = path.join(process.cwd(), filePath);
                res.sendFile(filePath, isStaticOrData);
            }
        };
        res.sendFile = (filePath, isStatic) => {
            if (res.finished)
                return;

            var contentType = res.getHeader('Content-Type');
            if (!contentType) {
                contentType = sysConfig.mime[fileUtil.getFileExt(filePath)] || '';
            }
            if (contentType == '') {
                res.send404();
                return;
            }

            fs.exists(filePath, exists => {
                if (res.finished)
                    return;

                if (exists) {
                    var stat = fs.statSync(filePath);
                    if (stat.isFile() && stat.size <= sysConfig.downloadFileSize) {
                        var etag = this.getEtag(stat);
                        var lastModified = stat.mtime.toUTCString();
                        if (isStatic && !res.disableCache && req.headers['if-none-match'] == etag && lastModified !== req.headers['if-modified-since']) {
                            res.statusCode = 304;
                            res.contentType = contentType;
                            res.end();
                        } else {
                            if (isStatic || res.disableCache) {
                                res.setHeader("Etag", etag);
                                res.setHeader('Last-Modified', lastModified);
                                if (res.disableCache)
                                    res.setHeader('Cache-Control', 'no-cache');
                                else
                                    res.setHeader('Cache-Control', sysConfig.fileCacheControl);
                            }

                            if (stat.size > sysConfig.downloadBufferSize) {
                                res.setHeader("Accept-Ranges", "bytes");
                            }

                            var start = 0;
                            var end = stat.size - 1;
                            var range = req.headers.range;
                            if (range) {
                                var result = range.match(/bytes=(\d*)-(\d*)/);
                                if (result) {
                                    start = isNaN(result[1]) ? start : parseInt(result[1]);
                                    end = !isNaN(result[2]) ? end : parseInt(result[2]) - 1;
                                }
                                res.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + stat.size);
                                res.statusCode = 206;
                                res.contentType = contentType;
                            } else {
                                res.statusCode = 200;
                                res.contentType = contentType;
                            }

                            if (end < start) {
                                res.end();
                                return;
                            }

                            var encoding = 'binary';
                            if (!res.isBinary && (res.contentType.startsWith('text/') || res.contentType.endsWith('/json'))) {
                                encoding = 'utf-8';
                            }
                            var fReadStream = fs.createReadStream(filePath, {
                                start: start,
                                end: end,
                                encoding: encoding,
                                bufferSize: sysConfig.downloadBufferSize
                            });
                            fReadStream.on('data', chunk => {
                                res.write(chunk, encoding);
                            });
                            fReadStream.on('end', () => {
                                res.end();
                            });
                        }
                    } else {
                        res.send404();
                    }
                } else {
                    res.send404();
                }
            });
        };
        res.send404 = () => {
            if (res.finished)
                return;

            if (!res.headersSent) {
                res.removeHeaders();
                res.statusCode = 404;
                res.contentType = 'text/html';
            }
            if (sysConfig.page404 != '') {
                fs.readFile(sysConfig.page404, 'utf-8', (err, data) => {
                    if (res.finished)
                        return;

                    if (err) {
                        res.write('<h3>HTTP 404 - Not Found.</h3>');
                        res.write('<p>请求地址：' + req.url + '</p>');
                    } else {
                        res.write(data);
                    }
                    res.end();
                });
            } else {
                res.write('<h3>HTTP 404 - Not Found.</h3>');
                res.write('<p>请求地址：' + req.url + '</p>');
                res.end();
            }
        };
        res.send500 = err => {
            if (res.finished)
                return;

            if (err)
                logger.error(req.method, req.url, '\n', req.headers, '\n', req.formBody, '\n', err);

            if (!res.headersSent) {
                res.removeHeaders();
                res.statusCode = 500;
                res.contentType = 'text/html';
            }
            if (sysConfig.page500 != '') {
                fs.readFile(sysConfig.page500, 'utf-8', (err, data) => {
                    if (res.finished)
                        return;

                    if (err) {
                        res.write('<h3>HTTP 500 - Internal Server Error.</h3>');
                        res.write('<p>请求地址：' + req.url + '</p>');
                    } else {
                        res.write(data);
                    }
                    res.end();
                });
            } else {
                res.write('<h3>HTTP 500 - Internal Server Error.</h3>');
                res.write('<p>请求地址：' + req.url + '</p>');
                res.end();
            }
        };
        res.redirect = (url, httpCode) => {
            if (res.finished)
                return;

            res.setHeader("Cache-Control", "no-cache");
            res.setHeader("Location", url);
            res.statusCode = httpCode || '302';
            res.end();
        };
        res.addCookie = (key, value, timeout) => {
            if (timeout > 0) {
                value += '; expires=' + new Date(Date.now() + timeout * 1000) + '; max-age=' + timeout;
            }
            res.setHeader('Set-Cookie', key + '=' + value + '; path=/; httpOnly=true');
        };
        res.removeCookie = key => {
            res.setHeader('Set-Cookie', key + '=; expires=' + new Date(Date.now() - 1000) + '; max-age=0; path=/; httpOnly=true');
        };
        res.removeCookies = () => {
            var cookies = req.cookies.getAll();
            for (var key in cookies) {
                res.setHeader('Set-Cookie', key + '=; expires=' + new Date(Date.now() - 1000) + '; max-age=0; path=/; httpOnly=true');
            }
        };
        res.removeHeaders = () => {
            res.getHeaderNames().forEach(item => {
                res.removeHeader(item);
            });
        }
    }

    writeOrEnd(sessionManager, interceptorAfter, chunk, encoding, callback, isEnd) {
        if (this.res.finished)
            return;

        if (!this.res.headersSent && !this.res.statusCode)
            this.res.statusCode = 200;
        if (!this.res.headersSent && !this.res.contentType) {
            var fisrtChar = chunk ? chunk.toStr().substr(0, 1) : '';
            if (fisrtChar == '[' || fisrtChar == '{')
                this.res.contentType = 'application/json';
            else
                this.res.contentType = 'text/html';
        }

        var _res = Object.create(null);
        for (var item in this.res) {
            if (typeof this.res[item] !== 'function' && typeof this.res[item] !== 'object') {
                _res[item] = this.res[item];
            }
        }
        interceptorAfter(this.req, _res, chunk, chunk => {
            this.sendHeader();

            if (isEnd && this.req.session._updated) {
                sessionManager.store(this.req.njsid, this.req.session, () => {
                    if (this.res.finished)
                        return;

                    this.end.call(this.res, chunk, encoding, callback);
                });
            } else if (isEnd) {
                this.end.call(this.res, chunk, encoding, callback);
            } else {
                this.write.call(this.res, chunk, encoding, callback);
            }
        });
    }

    sendHeader() {
        if (!this.res.headersSent) {
            if (this.res.contentType) {
                var charset = '';
                if (!this.res.isBinary && (this.res.contentType.startsWith('text/') || this.res.contentType.endsWith('/json'))) {
                    charset = '; charset=utf-8';
                }
                this.res.setHeader('Content-Type', this.res.contentType + charset);
            }
            this.res.writeHead(this.res.statusCode, Object.assign({}, sysConfig.header, {
                'Server': 'njserver/' + __njserverVersion,
                'X-Powered-By': 'verup@qq.com'
            }));
        }
    }

    getEtag(stat) {
        const mtime = stat.mtime.getTime().toString(16);
        const size = stat.size.toString(16);
        return `W/"${size}-${mtime}"`;
    }
};

module.exports = response;